﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

namespace HTTPServer
{
    class Server
    {
        Socket serverSocket;

        public Server(int portNumber, string redirectionMatrixPath)
        {
            this.LoadRedirectionRules(redirectionMatrixPath);
            //TODO: initialize this.serverSocket
            this.serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, portNumber);
            this.serverSocket.Bind(endpoint);
        }

        public void StartServer()
        {
            // TODO: Listen to connections, with large backlog.
            this.serverSocket.Listen(100);
            // TODO: Accept connections in while loop and start a thread for each connection on function "Handle Connection"
            while (true)
            {
                //TODO: accept connections and start thread for each accepted connection.
                Socket clientSocket = this.serverSocket.Accept();
                Thread thread = new Thread(new ParameterizedThreadStart(this.HandleConnection));
                thread.Start(clientSocket);
            }
        }

        public void HandleConnection(object obj)
        {
            Socket clientSocket = (Socket)obj;
            clientSocket.ReceiveTimeout = 0; //indicates an infinite time-out period.
            // TODO: receive requests in while true until remote client closes the socket.
            while (true)
            {
                try
                {
                    // TODO: Receive request
                    byte[] requestReceived = new byte[1024];
                    int receivedLen = clientSocket.Receive(requestReceived);
                    // TODO: break the while loop if receivedLen==0
                    if (receivedLen == 0)
                    {
                        break;
                    }
                    // TODO: Create a Request object using received request string
                    string requestStr = Encoding.ASCII.GetString(requestReceived, 0, receivedLen);
                    Request request = new Request(requestStr);
                    // TODO: Call HandleRequest Method that returns the response
                    Response respone = HandleRequest(request);
                    // TODO: Send Response back to client
                    clientSocket.Send(Encoding.ASCII.GetBytes(respone.ResponseString));
                }
                catch (Exception ex)
                {
                    Logger.LogException(ex);
                }
            }

            clientSocket.Close();
        }

        Response HandleRequest(Request request)
        {
            string content;
            try
            {
                //TODO: check for bad request 

                if (!request.ParseRequest())
                {
                    content = LoadDefaultPage(Configuration.BadRequestDefaultPageName);
                    return new Response(StatusCode.BadRequest, "text/html", content, null);
                }

                //TODO: map the relativeURI in request to get the physical path of the resource.

                string filePath = Configuration.RootPath + "\\" + request.relativeURI;
                //TODO: check for redirect

                string fileName = Path.GetFileName(request.relativeURI);
                string redirectionPath = this.GetRedirectionPagePathIFExist(fileName);

                //TODO: check file exists
                if (!string.IsNullOrEmpty(redirectionPath))
                {
                    content = LoadDefaultPage(Configuration.RedirectionDefaultPageName);
                    return new Response(StatusCode.Redirect, "text/html", content, redirectionPath);
                }

                //TODO: read the physical file

                if (File.Exists(filePath))
                {
                    StreamReader sr = new StreamReader(filePath);
                    content = sr.ReadToEnd();
                }
                else
                {
                    content = LoadDefaultPage(Configuration.NotFoundDefaultPageName);
                    return new Response(StatusCode.NotFound, "text/html", content, null);
                }

                // Create OK response
                return new Response(StatusCode.OK, "text/html", content, null);

            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                content = LoadDefaultPage(Configuration.InternalErrorDefaultPageName);
                return new Response(StatusCode.InternalServerError, "text/html", content, null);
            }
        }


        /// <summary>
        /// Gets the redirection page path IF exist.
        /// </summary>
        /// <param name="relativePath">The relative path.</param>
        /// <returns>The redirection path, empty string otherwise</returns>
        private string GetRedirectionPagePathIFExist(string relativePath)
        {
            if (Configuration.RedirectionRules.ContainsKey(relativePath))
            {
                return Configuration.RedirectionRules[relativePath];
            }

            return string.Empty;
        }

        private string LoadDefaultPage(string defaultPageName)
        {
            string filePath = Path.Combine(Configuration.RootPath, defaultPageName);
            if (!File.Exists(filePath))
            {
                Logger.LogException(new Exception("Default Page " + defaultPageName + " doesn't exist"));
                return string.Empty;
            }
            StreamReader reader = new StreamReader(filePath);
            string file = reader.ReadToEnd();
            reader.Close();
            return file;

        }

        /// <summary>
        /// Loads the redirection rules from the configuration file.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        private void LoadRedirectionRules(string filePath)
        {
            try
            {
                StreamReader reader = new StreamReader(filePath);


                Configuration.RedirectionRules = new Dictionary<string, string>();

                while (!reader.EndOfStream)
                {
                    string temp = reader.ReadLine();
                    string[] result = temp.Split(',');
                    Configuration.RedirectionRules.Add(result[0], result[1]);
                }

                reader.Close();
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);

                Environment.Exit(1);
            }
        }


    }
}
